//-----------------------------------------------------------------------
// <copyright file="Publication.cs" company="iron9light">
//      Copyright (c) iron9light. All rights reserved.
// </copyright>
// <author>iron9light</author>
//-----------------------------------------------------------------------

namespace EventBroker
{
    using System;
    using System.Globalization;
    using System.Reflection;

    /// <summary>
    /// Represents a publication for a topic and handles the publication event.
    /// </summary>
    public class Publication : IPublication
    {
        private readonly WeakReference weakRefPublisher;
        private readonly string eventName;
        private IEventTopic eventTopic;

        /// <summary>
        /// Initializes a new instance of the <see cref="Publication"/> class
        /// </summary>
        /// <param name="publisher">The publisher.</param>
        /// <param name="eventName">Name of the event.</param>
        public Publication(object publisher, string eventName)
        {
            this.weakRefPublisher = new WeakReference(publisher);
            this.eventName = eventName;

            EventInfo publishedEvent = GetEventInfo(publisher, eventName);

            ThrowIfEventNotExist(publishedEvent, eventName);
            ThrowIfInvalidEventHandler(publishedEvent);
            ThrowIfEventIsStatic(publishedEvent);

            Delegate handler = this.CreateEventHandler(publishedEvent);
            publishedEvent.AddEventHandler(publisher, handler);
        }

        /// <summary>
        /// Gets or sets the event topic.
        /// </summary>
        /// <value>The event topic.</value>
        public IEventTopic EventTopic
        {
            get
            {
                return this.eventTopic;
            }

            set
            {
                this.eventTopic = value;
            }
        }

        /// <summary>
        /// Gets the publisher of the event.
        /// </summary>
        public object Publisher
        {
            get { return this.weakRefPublisher.Target; }
        }

        /// <summary>
        /// Gets the name of the event on the <see cref="Publication.Publisher"/>.
        /// </summary>
        public string EventName
        {
            get { return this.eventName; }
        }

        /// <summary>
        /// Fires the event publication.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        public void PublicationHandler(object sender, EventArgs e)
        {
            if (this.eventTopic != null)
            {
                this.eventTopic.Fire(this, sender, e);             
            }
        }

        /// <summary>
        /// See <see cref="IDisposable.Dispose"/> for more information.
        /// </summary>
        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Releases unmanaged and - optionally - managed resources
        /// </summary>
        /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                object publisher = this.Publisher;
                if (publisher != null)
                {
                    EventInfo publishedEvent = GetEventInfo(publisher, this.eventName);
                    Delegate eventHandler = this.CreateEventHandler(publishedEvent);
                    publishedEvent.RemoveEventHandler(publisher, eventHandler);
                }
            }
        }

        /// <summary>
        /// Gets the event info.
        /// </summary>
        /// <param name="publisher">The publisher.</param>
        /// <param name="eventName">Name of the event.</param>
        /// <returns>The event info.</returns>
        private static EventInfo GetEventInfo(object publisher, string eventName)
        {
            return publisher.GetType().GetEvent(
                    eventName,
                    BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
        }

        /// <summary>
        /// Throws if event does not exist.
        /// </summary>
        /// <param name="publishedEvent">The published event.</param>
        /// <param name="eventName">Name of the event.</param>
        private static void ThrowIfEventNotExist(EventInfo publishedEvent, string eventName)
        {
            if (publishedEvent == null)
            {
                throw new EventBrokerException(
                        String.Format(
                                CultureInfo.CurrentCulture,
                                Properties.Resources.CannotFindPublishedEvent,
                                eventName));
            }
        }

        /// <summary>
        /// Throws if event is static.
        /// </summary>
        /// <param name="eventInfo">The published event.</param>
        private static void ThrowIfEventIsStatic(EventInfo eventInfo)
        {
            if (eventInfo.GetAddMethod().IsStatic || eventInfo.GetRemoveMethod().IsStatic)
            {
                throw new EventBrokerException(
                        String.Format(
                                CultureInfo.CurrentCulture,
                                Properties.Resources.StaticPublisherNotAllowed,
                                eventInfo.Name));
            }
        }

        /// <summary>
        /// Throws if invalid event handler.
        /// </summary>
        /// <param name="eventInfo">The event info.</param>
        private static void ThrowIfInvalidEventHandler(EventInfo eventInfo)
        {
            MethodInfo methodInfo = eventInfo.EventHandlerType.GetMethod("Invoke");

            if (methodInfo.ReturnType == typeof(void))
            {
                ParameterInfo[] parameters = methodInfo.GetParameters();
                if (parameters.Length == 2)
                {
                    if (typeof(object).IsAssignableFrom(parameters[0].ParameterType) && typeof(EventArgs).IsAssignableFrom(parameters[1].ParameterType))
                    {
                        return;
                    }
                }
            }

            throw new EventBrokerException(
                    String.Format(
                            CultureInfo.CurrentCulture,
                            Properties.Resources.InvalidPublicationSignature,
                            eventInfo.DeclaringType.FullName,
                            eventInfo.Name));
        }

        /// <summary>
        /// Creates the event handler.
        /// </summary>
        /// <param name="eventInfo">The event info.</param>
        /// <returns>The event handler delegate.</returns>
        private Delegate CreateEventHandler(EventInfo eventInfo)
        {
            return Delegate.CreateDelegate(
                    eventInfo.EventHandlerType,
                    this,
                    this.GetType().GetMethod("PublicationHandler"));
        }
    }
}